<?php
/*
* Captcha Class base on PHP GD Lib
* include('captchaClass.php');
* $captchaDemo=new Captcha();
* $captchaDemo->createImage();
*/
class Captcha{
    //@定义验证码图片高度
    private $height;
    //@定义验证码图片宽度
    private $width;
    //@定义验证码字符个数
    private $textNum;
    //@定义验证码字符内容
    private $textContent;
    //@定义字符颜色
    private $fontColor;
    //@定义随机出的文字颜色
    private $randFontColor;
    //@定义字体大小
    private $fontSize;
    //@定义字体
    private $fontFamily;
    //@定义背景颜色
    private $bgColor;
    //@定义随机出的背景颜色
    private $randBgColor;
    //@定义字符语言
    private $textLang;
    //@定义干扰点数量
    private $noisePoint;
    //@定义干扰线数量
    private $noiseLine;
    //@定义是否扭曲
    private $distortion;
    //@定义扭曲图片源
    private $distortionImage;
    //@定义是否有边框
    private $showBorder;
    //@定义验证码图片源
    private $image;
    //@定义过期时间
    private $expire_time;

    //加密KEY
    protected $key='浮沉@2722053503';

    //@Constructor 构造函数
    public function __construct(){
//        session_start();
        $this->textNum=4;
        $this->fontSize=16;
        $this->fontPath='public/ttf/';//设置中文字体
        $this->fontFamily=$this->fontFamily();
        $this->textLang='en';
        $this->noisePoint=30;
        $this->noiseLine=3;
        $this->distortion=false;
        $this->showBorder=false;
    }



    //@设置图片宽度
    public function setWidth($w){
        $this->width=$w;
    }

    //@设置图片高度
    public function setHeight($h){
        $this->height=$h;
    }

    //@设置字符个数
    public function setTextNumber($textN){
        $this->textNum=$textN;
    }

    //@设置字符颜色
    public function setFontColor($fc){
        $this->fontColor=sscanf($fc,'#%2x%2x%2x');
    }

    //@设置字号
    public function setFontSize($n){
        $this->fontSize=$n;
    }

    //@设置字体
    public function setFontFamily($ffUrl){
        $this->fontFamily=$ffUrl;
    }

    //@设置字符语言
    public function setTextLang($lang){
        $this->textLang=$lang;
    }

    //@设置图片背景
    public function setBgColor($bc){
        $this->bgColor=sscanf($bc,'#%2x%2x%2x');
    }

    //@设置干扰点数量
    public function setNoisePoint($n){
        $this->noisePoint=$n;
    }

    //@设置干扰线数量
    public function setNoiseLine($n){
        $this->noiseLine=$n;
    }

    //@设置是否扭曲
    public function setDistortion($b){
        $this->distortion=$b;
    }

    //@设置是否显示边框
    public function setShowBorder($border){
        $this->showBorder=empty($border)?$border:sscanf($border,'#%2x%2x%2x');
    }

    public function setExpire_time($expire_time){
        $this->expire_time = $expire_time;
    }


    //@初始化验证码图片
    public function initImage(){
        if(empty($this->width)){$this->width=floor($this->fontSize*1.3)*$this->textNum+10;}
        if(empty($this->height)){$this->height=$this->fontSize*2;}
        $this->image=imagecreatetruecolor($this->width,$this->height);


        if(empty($this->bgColor)){
            $this->randBgColor=imagecolorallocate($this->image,mt_rand(100,255),mt_rand(100,255),mt_rand(100,255));
        }else{
            $this->randBgColor=imagecolorallocate($this->image,$this->bgColor[0],$this->bgColor[1],$this->bgColor[2]);
        }

        imagefill($this->image,0,0,$this->randBgColor);
    }

    //@产生随机字符
    public function randText($type){
        $string='';
        switch($type){
            case 'en':
                $str='ABCDEFGHJKLMNPQRSTUVWXY3456789';
                for($i=0;$i<$this->textNum;$i++){
                    $string=$string.','.$str[mt_rand(0,29)];
                }
                break;
            case 'cn':
                for($i=0;$i<$this->textNum;$i++) {
                    $string=$string.','.chr(rand(0xB0,0xCC)) .chr(rand(0xA1,0xBB));
                }

                $string=iconv('GB2312','UTF-8',$string); //转换编码到utf8
                break;
        }
        return substr($string,1);
    }

    //@输出文字到验证码
    public function createText(){
        $textArray=explode(',',$this->randText($this->textLang));



        $this->textContent=join('',$textArray);

        for($i=0;$i<$this->textNum;$i++){
            if(empty($this->fontColor)){
                $this->randFontColor=imagecolorallocate($this->image,mt_rand(0,100),mt_rand(0,100),mt_rand(0,100));
            }else{
                $this->randFontColor=imagecolorallocate($this->image,$this->fontColor[0],$this->fontColor[1],$this->fontColor[2]);
            }
            $angle=mt_rand(-1,1)*mt_rand(1,20);
            imagettftext($this->image,$this->fontSize,$angle,5+$i*floor($this->fontSize*1.3),floor($this->height*0.75),$this->randFontColor,$this->fontFamily,$textArray[$i]);
        }
    }

    //@生成干扰点
    public function createNoisePoint(){
        for ($i=0; $i<=$this->noisePoint;$i++) {
            $codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
            //杂点颜色
            $noiseColor = imagecolorallocate($this->image, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
            // 绘杂点
            imagestring($this->image, 5, mt_rand(-10, $this->width), mt_rand(-10, $this->height), $codeSet[mt_rand(0, 29)], $noiseColor);
        }
    }


    //@产生干扰线
    private function createNoiseLine(){
        for ($i=0;$i<$this->noiseLine;$i++){
            $this->writeCurve();
        }
    }
    /**
     * 引用ThinkPHP 干扰线 函数
     * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
     *
     *      高中的数学公式咋都忘了涅，写出来
     *        正弦型函数解析式：y=Asin(ωx+φ)+b
     *      各常数值对函数图像的影响：
     *        A：决定峰值（即纵向拉伸压缩的倍数）
     *        b：表示波形在Y轴的位置关系或纵向移动距离（上加下减）
     *        φ：决定波形与X轴位置关系或横向移动距离（左加右减）
     *        ω：决定周期（最小正周期T=2π/∣ω∣）
     *
     */
    private function writeCurve()
    {
        $px = $py = 0;
        $this->imageH = $this->height;
        $this->imageW = $this->width;
        $this->fontSize = 9.5;
        $this->color =imagecolorallocate($this->image,mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
        // 曲线前部分
        $A = mt_rand(1, $this->imageH / 2); // 振幅
        $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量
        $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
        $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
        $w = (2 * M_PI) / $T;

        $px1 = 0; // 曲线横坐标起始位置
        $px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置

        for ($px = $px1; $px <= $px2; $px = $px + 1) {
            if (0 != $w) {
                $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
                $i  = (int) ($this->fontSize / 5);
                while ($i > 0) {
                    imagesetpixel($this->image, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出（不用这while循环）性能要好很多
                    $i--;
                }
            }
        }

        // 曲线后部分
        $A   = mt_rand(1, $this->imageH / 2); // 振幅
        $f   = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
        $T   = mt_rand($this->imageH, $this->imageW * 2); // 周期
        $w   = (2 * M_PI) / $T;
        $b   = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
        $px1 = $px2;
        $px2 = $this->imageW;

        for ($px = $px1; $px <= $px2; $px = $px + 1) {
            if (0 != $w) {
                $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
                $i  = (int) ($this->fontSize / 5);
                while ($i > 0) {
                    imagesetpixel($this->image, $px + $i, $py + $i, $this->color);
                    $i--;
                }
            }
        }
    }

    //波动曲线
    private function wirteSinLine($color,$w)
    {
        $img= $this->image;
        $h= $this->height;
        $h1=rand(-5,10);
        $h2=rand(-1,3);
        $w2=rand(9,20);
        $h3=rand(4,10);

        for($i=-$w/2;$i<$w/2;$i=$i+0.1)
        {
            $y=$h/$h3*sin($i/$w2)+$h/2+$h1;


            $pointColor=imagecolorallocate($this->image,mt_rand(0,255),mt_rand(0,255),mt_rand(0,255));
            imagesetpixel($img,$i+$w/2,$y,$pointColor);
            $h2!=0?@imagesetpixel($img,$i+$w/2,$y+$h2,$color):null;
        }
    }


    //@扭曲文字
    public function distortionText(){
        $this->distortionImage=imagecreatetruecolor($this->width,$this->height);
        imagefill($this->distortionImage,0,0,$this->randBgColor);
        for($x=0;$x<$this->width;$x++){
            for($y=0;$y<$this->height;$y++){
                $rgbColor=imagecolorat($this->image,$x,$y);
                imagesetpixel($this->distortionImage,(int)($x+sin($y/$this->height*2*M_PI-M_PI*0.5)*3),$y,$rgbColor);
            }
        }
        $this->image=$this->distortionImage;
    }

    //随机字体
    private function fontFamily(){
        $fontPath= $this->fontPath;
        $fileArr = scandir($fontPath);
        return $fontPath.$fileArr[rand(2,count($fileArr)-1)];
    }

    //@生成验证码图片
    public function createImage(){
        $this->initImage(); //创建基本图片
        $this->createText(); //输出验证码字符
        if($this->distortion){$this->distortionText();} //扭曲文字
        $this->createNoisePoint(); //产生干扰点
        $this->createNoiseLine(); //产生干扰线
        if($this->showBorder){ //添加边框
            $borderColor=imagecolorallocate($this->image,$this->showBorder[0],$this->showBorder[1],$this->showBorder[2]);
            imagerectangle($this->image,0,0,$this->width-1,$this->height-1,$borderColor);
        }
        imagepng($this->image);
        imagedestroy($this->image);
        if($this->distortion){@imagedestroy($this->distortionImage);}

        //记录code
        return $this->setCapCode($this->textContent);;
    }

    //记录session
    public function setCapCode($code){

        $data = ['code'=>true,];
        $data['code']=short_md5(md5(strtoupper($code)).$this->expire_time);


        //到期时间
        if (!empty($this->expire_time)){
            $data['expire_time']=strtotime("+{$this->expire_time}second");
        }



        $_SESSION['captcha'][short_md5($this->key)] =$data;
        return true;
    }
    public function check($code=null){


        $code = short_md5(md5(strtoupper($code)).$this->expire_time);



        $checkArr = $_SESSION['captcha'][short_md5($this->key)];




        $_SESSION['captcha'][short_md5($this->key)]=[];
        if (empty($checkArr['code']))return false;

        if (@$checkArr['code']==$code){

            //失效
            if (!empty($this->expire_time)){

                if (time()>$checkArr['expire_time']){
                    return "验证码失效";
                }
            }

            return true;

        }else{
            return false;
        }


    }


}
?>
